#!/usr/bin/perl

# Copyright (c) 2011, 2025, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation.  The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA

# -*- cperl -*-
#
# MySQL Cluster compile script to bridge the gap between
# different build systems in different versions of MySQL Server
#
# This script is intended for internal use
#
use strict;
use Cwd qw[abs_path cwd];
use File::Basename;
use Getopt::Long;
use IPC::Cmd qw[can_run];

# Automatically flush STDOUT
select(STDOUT);
$| = 1;

# Only add the command line options handled by this script, 
# thus acting like a filter and passing all other arguments
# straight through
my $opt_debug;
my $opt_build_type;
my $opt_build = 1;
if ($^O eq "cygwin" or $^O eq "MSWin32" or $^O eq "msys")
{
  # Default to not build on Windows, just configure a solution
  # which can then be opened in VS
  $opt_build = 0;
}
my $opt_embedded = 0;
my $opt_just_print;
my $opt_vanilla;
my $opt_autotest;
my $opt_valgrind;
my $opt_distcheck;
my $opt_quick = 1;
Getopt::Long::Configure("pass_through");
GetOptions(

  # Build MySQL Server and NDB with debug
  'debug!' => \$opt_debug,
  'with-debug:s' => sub { $opt_debug = 1; },
  'build-type=s' => \$opt_build_type,
  'build!' => \$opt_build,
  'c|just-configure' => sub { $opt_build = 0; },
  'n|just-print' => \$opt_just_print,
  'vanilla' => \$opt_vanilla,
  'autotest' => \$opt_autotest,
  'valgrind' => \$opt_valgrind,
  'distcheck' => \$opt_distcheck,
  'embedded' => sub { $opt_embedded = 1; },
  'quick=s' => \$opt_quick

) or exit(1);

# Find source root directory, assume this script is
# in <srcroot>/storage/ndb/
my $opt_srcdir = dirname(dirname(dirname(abs_path($0))));
die unless -d $opt_srcdir; # Sanity check that the srcdir exist
if ($^O eq "cygwin" or $^O eq "msys") {
  # Convert posix path to Windows mixed path since cmake
  # is most likely a windows binary
  $opt_srcdir= `cygpath -m $opt_srcdir`;
  chomp $opt_srcdir;
}

# Check that cmake exists and figure out it's version
my $cmake = can_run("cmake3") || can_run("cmake");

my $cmake_version_id;
{
  my $version_text = `$cmake --version`;
  print $version_text;
  die "Could not find cmake" if ($?);
  if ( $version_text =~ /^cmake[3]? version ([0-9]*)\.([0-9]*)\.*([^\s]*)/ )
  {
    #print "1: $1 2: $2 3: $3\n";
    $cmake_version_id= $1*10000 + $2*100 + $3;
    #print "cmake_version_id: $cmake_version_id\n";
  }
  die "Could not parse cmake version" unless ($cmake_version_id);
}

# Replace gcc with g++ in CXX environment variable
if(defined $ENV{"CXX"} and $ENV{"CXX"} =~ m/gcc/)
{
  my $old_cxx= $ENV{"CXX"};
  $ENV{"CXX"} =~ s/gcc/g++/;
  print("compile-cluster: switched CXX compiler from '$old_cxx' to '$ENV{CXX}'\n");
}

# Remove -fno-exceptions from CXXFLAGS environment variable
if(defined $ENV{"CXXFLAGS"} and $ENV{"CXXFLAGS"} =~ "-fno-exceptions")
{
  $ENV{"CXXFLAGS"} =~ s/-fno-exceptions//g;
  print("compile-cluster: stripped off -fno-exceptions from CXXFLAGS='$ENV{CXXFLAGS}'\n");
}

# If no libmysqld directory in source, there is support for build embedded server.
if(! -d "$opt_srcdir/libmysqld")
{
  if($opt_embedded)
  {
    die "Build with embedded server is not possible";
  }
  $opt_embedded = undef;
}

#
# Configure
#
{
  # Remove old CMakeCache.txt(ignore if not exists) to
  # force fresh configure
  unlink("CMakeCache.txt");

  my @args;
  if ($opt_debug)
  {
    print("compile-cluster: debug build requested\n");
    push(@args, "-DWITH_DEBUG=1");
    # The MySQL maintainter mode is automatically turned on when
    # building with debug using gcc on linux, turn it off
    # by uncommenting the line below or use --mysql-maintainer-mode=0
    # push(@args, "-DMYSQL_MAINTAINER_MODE=0");
  }

  if ($opt_vanilla)
  {
    # Build MySQL without any NDB functionality
    print("compile-cluster: vanilla build requested, no sugar\n");
    push(@args, "-DWITH_NDBCLUSTER_STORAGE_ENGINE=0");
  }
  else
  {
    # Hardcoded options controlling how to build MySQL Server
    push(@args, "-DWITH_SSL=system"); # Consistent error messages
    push(@args, "-DWITH_NDB=1"); # Build MySQL Cluster

    # Hardcoded options controlling how to build NDB
    push(@args, "-DWITH_NDB_TEST=1");
    push(@args, "-DWITH_NDBAPI_EXAMPLES=1")
      unless ($^O eq "cygwin" or $^O eq "MSWin32")
  }

  if ($opt_quick)
  {
    # Hardcoded options removing an arbitrary set of slow to build
    # unrelated components
    push(@args, "-DWITH_ROUTER=0"); # No MySQL Router
    push(@args, "-DWITH_MEB=0"); # No MEB
    push(@args, "-DWITH_RAPID=0"); # No RAPID
    push(@args, "-DWITH_GROUP_REPLICATION=0"); # No GR

    # To speed up build it's possible to skip building the
    # unittests with "-DWITH_UNIT_TESTS=0", however those
    # are important and certainly well worth the time spent
    # to detect problems early and should thus not be skipped
    # by default.
  }

  if ($opt_autotest)
  {
    print("compile-cluster: autotest build requested, extra everything\n");
    push(@args, "-DWITH_NDB_CCFLAGS='-DERROR_INSERT'");
  }

  if (defined $opt_embedded)
  {
    push(@args, "-DWITH_EMBEDDED_SERVER=$opt_embedded");
  }

  if ($opt_valgrind)
  {
    print("compile-cluster: valgrind build requested, adjusting the knobs\n");
    # Add HAVE_purify to compiler flags in order to silence
    # inspected warnings
    push(@args, "-DCMAKE_C_FLAGS='-DHAVE_purify'");
    push(@args, "-DCMAKE_CXX_FLAGS='-DHAVE_purify'");
    # Turn on use of the valgrind headers to enhance detection
    push(@args, "-DWITH_VALGRIND=1");
  }

  # The cmake generator to use
  if ($opt_build_type)
  {
    push(@args, "-G \"$opt_build_type\"");
  }

  if ($^O eq "cygwin" or $^O eq "MSWin32" or $^O eq "msys")
  {
    # Default to x64 since that's currrenlty the only supported
    # architecture for building MySQL on Windows
    push(@args, "-A x64");
  }

  # Sets installation directory,  bindir, libdir, libexecdir etc.
  # The equivalent CMake variables are given without prefix
  # e.g if --prefix is /usr and --bindir is /usr/bin
  # then cmake variable (INSTALL_BINDIR) must be just "bin"
  my $opt_prefix;
  sub set_installdir
  {
    my($path, $varname) = @_;
    my $prefix_length = length($opt_prefix);
    if (($prefix_length > 0) && (index($path,$opt_prefix) == 0))
    {
      # path is under the prefix, remove the prefix and
      # maybe following "/"
      $path = substr($path, $prefix_length);
      if(length($path) > 0)
      {
        my $char = substr($path, 0, 1);
        if($char eq "/")
        {
          $path= substr($path, 1);
        }
      }
      if(length($path) > 0)
      {
        push(@args, "-D$varname=$path");
      }
    }
  }

  # Process --configure style arguments which need special conversion 
  my $opt_bindir;
  my $opt_libdir;
  my $opt_libexecdir;
  my $opt_includedir;
  my $opt_with_zlib_dir;
  my $opt_with_ssl;
  my $opt_localstatedir;
  my $opt_mysql_maintainer_mode;
  my $opt_with_gcov;
  my $opt_with_comment;
  my $opt_with_plugins;
  my $opt_without_plugin;
  my $opt_extra_charsets;
  my $opt_with_extra_charsets;
  my $opt_force_insource_build;
  Getopt::Long::Configure("pass_through");
  GetOptions(
    'prefix=s' => \$opt_prefix,
    'srcdir=s' => \$opt_srcdir,
    'bindir=s' => \$opt_bindir,
    'libdir=s' => \$opt_libdir,
    'libexecdir=s' => \$opt_libexecdir,
    'includedir=s' => \$opt_includedir,
    'with-zlib-dir=s' => \$opt_with_zlib_dir,
    'with-ssl:s' => \$opt_with_ssl,
    'localstatedir=s' => \$opt_localstatedir,
    'mysql-maintainer-mode=s' => \$opt_mysql_maintainer_mode,
    'with-gcov' => \$opt_with_gcov,
    'with-comment=s' => \$opt_with_comment,
    'with-plugins=s' => \$opt_with_plugins,
    'without-plugin=s' => \$opt_without_plugin,
    'with-extra-charsets=s' => \$opt_with_extra_charsets,
    'extra-charsets=s' => \$opt_extra_charsets,
    'force-insource-build' => \$opt_force_insource_build,
  ) or exit(1);

  if($opt_prefix)
  {
    push(@args, "-DCMAKE_INSTALL_PREFIX=$opt_prefix");
  }
  if($opt_bindir)
  {
    set_installdir($opt_bindir, "INSTALL_BINDIR");
  }
  if($opt_libdir)
  {
    set_installdir($opt_libdir, "INSTALL_LIBDIR");
  }
  if($opt_libexecdir)
  {
    set_installdir($opt_libexecdir, "INSTALL_SBINDIR");
  }
  if($opt_includedir)
  {
    set_installdir($opt_includedir, "INSTALL_INCLUDEDIR");
  }
  if($opt_with_zlib_dir)
  {
    $opt_with_zlib_dir = "system"
      if ($opt_with_zlib_dir ne "bundled");
    push(@args, "-DWITH_ZLIB=$opt_with_zlib_dir");
  }
  if($opt_with_ssl)
  {
    push(@args, "-DWITH_SSL=".($opt_with_ssl ? "yes" : "wolfssl"));
  }
  if ($opt_localstatedir)
  {
    push(@args, "-DMYSQL_DATADIR=$opt_localstatedir"); 
  }
  if ($opt_mysql_maintainer_mode)
  {
    push(@args, "-DMYSQL_MAINTAINER_MODE=" .
                 ($opt_mysql_maintainer_mode =~ /enable/ ? "1" : "0"));
  }
  if ($opt_with_gcov)
  {
    push(@args, "-DENABLE_GCOV=ON"); 
  }
  if ($opt_with_comment)
  {
    push(@args, "\"-DWITH_COMMENT=$opt_with_comment\""); 
  }
  if($opt_with_plugins)
  {
    my @plugins= split(/,/, $opt_with_plugins);
    foreach my $p (@plugins)
    {
      $p =~ s/-/_/g;
      push(@args, "-DWITH_".uc($p)."=1");
    }
  }
  if($opt_without_plugin)
  {
    push(@args, "-DWITHOUT_".uc($opt_without_plugin)."=1");
  }
  if ($opt_extra_charsets)
  {
    push(@args, "-DWITH_CHARSETS=$opt_extra_charsets"); 
  }
  if($opt_with_extra_charsets)
  {
    push(@args, "-DWITH_EXTRA_CHARSETS=$opt_with_extra_charsets");
  }
  if ($opt_force_insource_build)
  {
    push(@args, "-DFORCE_INSOURCE_BUILD=1");
  }

  # Default conversion of remaining args in ARGV from
  # 1) --arg          -> -DARG=1
  # 2) --arg=value    -> -DARG=value
  # 3) arg=value      -> environment variable arg=value
  foreach my $option (@ARGV)
  {
    if ($option =~  /^--/)
    {  
      # Remove leading --
      $option = substr($option, 2);
    
      my @v  = split('=', $option);
      my $name = shift(@v);
      $name = uc($name);
      $name =~ s/-/_/g;
      if (@v)
      {
        push(@args, "-D$name=".join('=', @v));
      }
      else
      {
        push(@args, "-D$name=1");
      }
    }
    else
    {
 
      # This must be environment variable
      my @v  = split('=', $option);
      my $name = shift(@v);
      if(@v)
      {
        $ENV{$name} = join('=', @v);  
      }
      else
      {
        die "unhandled argument '$option' found"; 
      }
    }
  }
  
  # The source directory to build from
  die "srcdir already contains CMakeCache.txt, this will not work!"
    if (-f "$opt_srcdir/CMakeCache.txt");
  push(@args, $opt_srcdir);

  if (cwd() eq $opt_srcdir && !$opt_force_insource_build)
  {
    die "
    Please do not build in-source.  Out-of source builds are highly
    recommended: you can have multiple builds for the same source, and there is
    an easy way to do cleanup, simply remove the build directory (note that
    'make clean' or 'make distclean' does *not* work)

    You *can* force in-source build by invoking compile-cluster with
    -force-insource-build option which in turn invokes cmake with
    -DFORCE_INSOURCE_BUILD=1";
  }

  if ($cmake_version_id < 30403)
  {
    die "CMake 3.4.3 or higher is required";
  }
  
  cmd($cmake, @args);
}

#
# Build
#
if (!$opt_build)
{
  print "Configuration completed, skipping build(used --no-build)\n";
  exit(0);
}

{
  # Use the universal "cmake --build <dir>" way of building
  # which is available from cmake 2.8 and works on all platforms
  my @args;
  push(@args, "--build");
  push(@args, ".");

  if ($^O eq "cygwin" or $^O eq "MSWin32" or $^O eq "msys")
  {
    # Choose to build RelWitDebInfo by default on Windows
    my $config = 'RelWithDebInfo';
    if ($opt_debug)
    {
      $config = 'Debug';
    }
    push(@args, "--config");
    push(@args, $config);
  }

  cmd($cmake, @args);

}

if ($opt_distcheck)
{
    print "\n";
    print "NOTE! 'make distcheck' not (yet) supported in this version\n";
    print "\n";
}

exit(0);


sub cmd {
  my ($cmd, @a)= @_;
  my $cmd_str = join(' ', $cmd, @a);
  print "compile-cluster: '$cmd_str'\n";
  return if ($opt_just_print);
  system($cmd_str)
    and print("command '$cmd_str' failed\n")
	  and exit(1);
}

